import os
import re
import queue
import time
import threading
import webbrowser
import requests
import tkinter as tk
from tkinter import messagebox, filedialog, ttk
from tkinterdnd2 import TkinterDnD, DND_FILES
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
import shutil

# Путь к файлу с токенами
TOKENS_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\токены для автопостинга.txt"
# Путь к файлу для неуспешных публикаций
FAILED_TOKENS_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\Токены неуспешных публикаций.txt"
# Путь к файлу для хранения последнего выложенного поста
LAST_POST_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\last_post.txt"
# Путь к папке с постами
POSTS_DIR = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\посты"
# Путь к папке для загруженных постов
UPLOADED_POSTS_DIR = os.path.join(POSTS_DIR, "загруженные")

# Пути к файлам для сохранения настроек
AUTOPOST_STATE_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\autopost_state.txt"
POST_LIMIT_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\post_limit_state.txt"
PHOTO_COUNT_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\photo_count_state.txt"


# Словарь для хранения информации о пользователях
user_info_dict = {}

# --- Dark Theme Colors ---
DARK_BACKGROUND = "#2b2b2b"         # Main window and frame background
DARK_FOREGROUND = "#dcdcdc"         # Default text color (light gray)
DARK_WIDGET_BG = "#3c3f41"          # Background for Entry, Text, Listbox
DARK_WIDGET_FG = "#bbbbbb"          # Text color for Entry, Text, Listbox content
DARK_INSERT_BG = "#dcdcdc"          # Cursor color in text widgets

DARK_BUTTON_BG = "#4a4d4f"          # Button background
DARK_BUTTON_FG = "#dcdcdc"          # Button text
DARK_BUTTON_ACTIVE_BG = "#5a5d5f"   # Button when active/hovered
DARK_BUTTON_PRESSED_BG = "#6a6d6f"  # Button when pressed

DARK_DISABLED_FG = "#777777"       # Disabled text/widget foreground
DARK_DISABLED_BG = "#3c3f41"       # Disabled widget background (e.g., button)

DARK_ACCENT_SUCCESS_BG = "#274e13" # Dark green for success messages background
DARK_ACCENT_FAILED_BG = "#592323"  # Dark red for failure messages background
DARK_SUCCESS_TEXT_COLOR = "#8fbc8f" # Light green for success text
DARK_FAILED_TEXT_COLOR = "#f08080"   # Light red for failure text

DARK_LINK_COLOR = "#6897bb"        # A softer blue for links on dark BG
DARK_PROGRESS_BAR = "#6897bb"      # Progress bar color (matches link color)
DARK_TROUGH_COLOR = "#313335"      # Scrollbar and Progressbar trough
DARK_HEADER_BG = "#3c3f41"         # Background for headers like Treeview.Heading
DARK_HEADER_FG = "#dcdcdc"         # Text for headers

DARK_SELECT_BG = "#54585a"         # Background for selected items in Listbox/Treeview
DARK_SELECT_FG = "#ffffff"         # Foreground for selected items

FONT_STYLE = ("Arial", 11) # Slightly smaller for better fit with dark theme
HEADER_FONT = ("Arial", 12, "bold")
# --- End Dark Theme Colors ---

# Очередь для сообщений из потоков
log_queue = queue.Queue()

# Флаг для остановки процесса публикации
stop_flag = threading.Event()

# Счётчик для уникальных тегов гиперссылок
hyperlink_counter = 0

# Функция для сортировки с учётом чисел в названиях файлов
def natural_key(filename):
    basename = os.path.basename(filename)
    return [text for text in re.split(r'(\d+)', basename)]

def get_user_info(token):
    url = 'https://api.vk.com/method/users.get'
    params = {
        'access_token': token,
        'v': '5.131'
    }
    response = requests.get(url, params=params)
    data = response.json()
    if 'response' in data:
        user_info = data['response'][0]
        user_info_dict[token] = f"{user_info['first_name']} {user_info['last_name']}"
        return user_info['id'], user_info_dict[token]
    elif 'error' in data and data['error'].get('error_code') == 5:
        raise Exception(f"Ошибка авторизации: {data['error']['error_msg']}")
    else:
        raise Exception("Ошибка получения информации о пользователе: " + str(data.get('error', {})))

def upload_photo(token, photo_path):
    url = 'https://api.vk.com/method/photos.getWallUploadServer'
    params = {
        'access_token': token,
        'v': '5.131'
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    upload_info = response.json()

    if 'response' not in upload_info:
        raise Exception("Ошибка получения сервера для загрузки фото: " + str(upload_info.get('error', {})))

    upload_url = upload_info['response']['upload_url']
    
    with open(photo_path, 'rb') as photo_file:
        files = {'photo': photo_file}
        upload_response = requests.post(upload_url, files=files)
        upload_response.raise_for_status()
        upload_data = upload_response.json()

    if 'photo' not in upload_data:
        raise Exception("Ошибка загрузки фото: " + str(upload_data))

    return upload_data

def save_photo(token, upload_data, user_id):
    url = 'https://api.vk.com/method/photos.saveWallPhoto'
    params = {
        'access_token': token,
        'user_id': user_id,
        'server': upload_data['server'],
        'photo': upload_data['photo'],
        'hash': upload_data['hash'],
        'v': '5.131'
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    data = response.json()
    if 'response' not in data:
        raise Exception("Ошибка сохранения фото: " + str(data.get('error', {})))
    return data

def upload_video(token, video_path):
    url = 'https://api.vk.com/method/video.save'
    params = {
        'access_token': token,
        'v': '5.131',
        'name': os.path.basename(video_path)
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    video_info = response.json()

    if 'response' not in video_info:
        raise Exception("Ошибка получения сервера для загрузки видео: " + str(video_info.get('error', {})))

    upload_url = video_info['response']['upload_url']
    
    with open(video_path, 'rb') as video_file:
        files = {'video_file': video_file}
        upload_response = requests.post(upload_url, files=files)
        upload_response.raise_for_status()
        # video.save's upload_url POST doesn't return JSON directly for requests library,
        # the actual video data is in video_info from the initial video.save call.
        # However, the API expects the video file to be POSTed to the upload_url.
        # After that, the video is processed. The video_info['response'] contains the video_id.
        # No further JSON is typically parsed from the file POST itself.
    if 'video_id' not in video_info['response']:
         raise Exception("Ошибка загрузки или сохранения видео: " + str(video_info.get('error', video_info)))
    return video_info['response']


def post_to_vk(token, user_id, message, attachments):
    url = 'https://api.vk.com/method/wall.post'
    params = {
        'access_token': token,
        'owner_id': user_id,
        'message': message,
        'attachments': attachments,
        'v': '5.131'
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    data = response.json()
    if 'error' in data:
        raise Exception("Ошибка публикации поста: " + data['error']['error_msg'])
    return data

def submit_post():
    stop_flag.clear()  
    post_button['state'] = tk.DISABLED
    stop_button['state'] = tk.NORMAL
    if autopost_var.get():
        try:
            post_limit = int(post_limit_var.get())
            photo_count = int(photo_count_var.get())
            if post_limit <= 0 or photo_count <= 0:
                messagebox.showerror("Ошибка", "Лимит постов и количество фотографий должны быть больше 0.")
                post_button['state'] = tk.NORMAL
                stop_button['state'] = tk.DISABLED
                return
            thread = threading.Thread(target=post_to_accounts_concurrently, args=(post_limit, photo_count))
        except ValueError:
            messagebox.showerror("Ошибка", "Введите корректные числовые значения для лимита постов и количества фотографий.")
            post_button['state'] = tk.NORMAL
            stop_button['state'] = tk.DISABLED
    else:
        thread = threading.Thread(target=post_to_accounts_concurrently)
    thread.start()

def stop_posting():
    stop_flag.set()
    log_queue.put("Публикация остановлена пользователем.\n")
    stop_button['state'] = tk.DISABLED
    post_button['state'] = tk.NORMAL
    add_schedule_button['state'] = tk.DISABLED if autopost_var.get() else tk.NORMAL

def update_add_schedule_button_state(*args):
    add_schedule_button['state'] = tk.DISABLED if autopost_var.get() else tk.NORMAL

def post_to_account(token, user_id_param, message, photos, videos):
    if stop_flag.is_set():
        return ("Публикация остановлена пользователем.", False, None)
    max_attempts = 5  # Изменено с 10 на 5
    attempt = 0
    user_name = ""
    actual_user_id = user_id_param

    while attempt < max_attempts:
        if stop_flag.is_set():
            return ("Публикация остановлена пользователем.", False, None)
        attempt += 1
        try:
            if actual_user_id is None or attempt == 1:
                actual_user_id, user_name = get_user_info(token)
            elif not user_name:
                _, user_name = get_user_info(token)

            log_queue.put(f"Публикация на аккаунте: {user_name if user_name else 'ID ' + str(actual_user_id)}... Попытка {attempt}\n")
            attachments = []

            for photo in photos:
                if stop_flag.is_set():
                    return ("Публикация остановлена пользователем.", False, None)
                log_queue.put(f"Загрузка фото: {os.path.basename(photo)} на аккаунт {user_name}...\n")
                upload_data = upload_photo(token, photo)
                saved_photo = save_photo(token, upload_data, actual_user_id)
                if 'response' in saved_photo:
                    attachments.append(f"photo{saved_photo['response'][0]['owner_id']}_{saved_photo['response'][0]['id']}")
                else:
                    raise Exception("Не удалось сохранить фото.")

            for video in videos:
                if stop_flag.is_set():
                    return ("Публикация остановлена пользователем.", False, None)
                log_queue.put(f"Загрузка видео: {os.path.basename(video)} на аккаунт {user_name}...\n")
                video_upload_response = upload_video(token, video)
                if 'video_id' in video_upload_response and 'owner_id' in video_upload_response:
                    attachments.append(f"video{video_upload_response['owner_id']}_{video_upload_response['video_id']}")
                elif 'video_id' in video_upload_response:
                    attachments.append(f"video{actual_user_id}_{video_upload_response['video_id']}")
                else:
                    raise Exception("Не удалось загрузить видео или получить video_id/owner_id.")

            result = post_to_vk(token, actual_user_id, message, ','.join(attachments))
            post_id = result['response']['post_id']
            link = f"https://vk.com/wall{actual_user_id}_{post_id}"
            return (f"Пост успешно опубликован на аккаунте: {user_name}", link, actual_user_id)
        except Exception as e:
            if attempt >= max_attempts:
                user_name_display = user_name if user_name else user_info_dict.get(token, token)
                error_message = f"Ошибка на аккаунте: {user_name_display}: {str(e)} после {max_attempts} попыток\n"
                return error_message, False, token
            else:
                user_name_display = user_name if user_name else user_info_dict.get(token, token)
                log_queue.put(f"Попытка {attempt} не удалась на аккаунте {user_name_display}. Ошибка: {e}\n")
                time.sleep(1)
    return ("Не удалось опубликовать после максимального количества попыток.", False, token)

def move_uploaded_files(files):
    os.makedirs(UPLOADED_POSTS_DIR, exist_ok=True)
    for file in files:
        try:
            shutil.move(file, os.path.join(UPLOADED_POSTS_DIR, os.path.basename(file)))
            log_queue.put(f"Файл перемещён: {os.path.basename(file)}\n")
        except Exception as e:
            log_queue.put(f"Ошибка при перемещении файла {os.path.basename(file)}: {e}\n")

def post_to_accounts_concurrently(post_limit=None, photo_count=None):
    global successful_posts_list, errors_list
    successful_posts_list = []
    errors_list = []
    failed_tokens_set = set()  
    accounts_posted_set = set()  

    if autopost_var.get() and post_limit and photo_count:
        if not os.path.exists(POSTS_DIR):
            log_queue.put(f"Папка {POSTS_DIR} не найдена.\n")
            post_button['state'] = tk.NORMAL
            stop_button['state'] = tk.DISABLED
            add_schedule_button['state'] = tk.DISABLED
            return
        all_photos = sorted([
            os.path.join(POSTS_DIR, f) for f in os.listdir(POSTS_DIR)
            if f.lower().endswith(('.jpg', '.jpeg', '.png'))
        ], key=natural_key)
        if not all_photos:
            log_queue.put("В папке 'посты' нет фотографий.\n")
            post_button['state'] = tk.NORMAL
            stop_button['state'] = tk.DISABLED
            add_schedule_button['state'] = tk.DISABLED
            return
        posting_schedule.clear()
        update_posting_schedule_table()
        for i in range(0, min(post_limit * photo_count, len(all_photos)), photo_count):
            photos = all_photos[i:i + photo_count]
            posting_schedule.append({'text': '', 'photos': photos, 'videos': []})
        log_queue.put(f"Сформировано {len(posting_schedule)} постов для автопубликации.\n")
        update_posting_schedule_table()

    if not tokens:
        log_queue.put("Нет доступных токенов для публикации.\n")
        post_button['state'] = tk.NORMAL
        stop_button['state'] = tk.DISABLED
        add_schedule_button['state'] = tk.DISABLED if autopost_var.get() else tk.NORMAL
        return

    if not posting_schedule:
        log_queue.put("Нет наборов публикаций для выполнения.\n")
        post_button['state'] = tk.NORMAL
        stop_button['state'] = tk.DISABLED
        add_schedule_button['state'] = tk.DISABLED if autopost_var.get() else tk.NORMAL
        return

    total_posts_to_make = len(posting_schedule) * len(tokens)
    progress_bar['maximum'] = total_posts_to_make
    progress_bar['value'] = 0

    output_text.config(state=tk.NORMAL)
    output_text.delete("1.0", tk.END)
    output_text.config(state=tk.DISABLED)

    successful_text.config(state=tk.NORMAL)
    successful_text.delete("1.0", tk.END)
    # successful_text.config(state=tk.DISABLED) # Keep normal for clickable links

    failed_text.config(state=tk.NORMAL)
    failed_text.delete("1.0", tk.END)
    failed_text.config(state=tk.DISABLED)


    successful_posts_count = 0
    failed_posts_count = 0
    actual_posts_attempted = 0


    for entry in posting_schedule:
        entry['photos'] = sorted(entry['photos'], key=natural_key)
        entry['videos'] = sorted(entry['videos'], key=natural_key)

    log_queue.put("Файлы отсортированы.\n")

    for entry_idx, entry in enumerate(posting_schedule):
        if stop_flag.is_set():
            break
        message = entry['text']
        photos = entry['photos']
        videos = entry['videos']
        
        successful_tokens_for_this_entry = 0
        
        valid_tokens_for_this_entry = [token for token in tokens if token not in failed_tokens_set]
        if not valid_tokens_for_this_entry:
            log_queue.put(f"Для публикации №{entry_idx + 1} нет доступных рабочих токенов.\n")
            progress_bar['value'] += len(tokens) - len(failed_tokens_set) 
            update_progress_label_threadsafe(successful_posts_count, failed_posts_count + (len(tokens) - len(failed_tokens_set)), total_posts_to_make)
            continue

        with ThreadPoolExecutor(max_workers=10) as executor:
            future_to_token = {
                executor.submit(post_to_account, token, None, message, photos, videos): token 
                for token in valid_tokens_for_this_entry
            }
            for future in as_completed(future_to_token):
                actual_posts_attempted +=1
                if stop_flag.is_set():
                    for f_in_loop in future_to_token: # renamed f to f_in_loop
                        if not f_in_loop.done():
                            f_in_loop.cancel()
                    break 
                token = future_to_token[future]
                try:
                    result = future.result()
                    if len(result) == 3:
                        if result[1] == False:
                            failed_posts_count += 1
                            update_failed_posts_threadsafe(result[0])
                            failed_tokens_set.add(result[2])
                        else:
                            successful_posts_count += 1
                            successful_tokens_for_this_entry +=1
                            accounts_posted_set.add(result[2])
                            update_successful_posts_threadsafe(result[0], result[1])
                    else:
                        failed_posts_count += 1
                        update_failed_posts_threadsafe(f"Неизвестная ошибка обработки результата для токена: {token_display_name(token)}.\n")
                        failed_tokens_set.add(token)
                    
                    progress_bar['value'] = successful_posts_count + failed_posts_count
                    update_progress_label_threadsafe(successful_posts_count, failed_posts_count, total_posts_to_make)

                except Exception as exc:
                    failed_posts_count += 1
                    user_name_display = user_info_dict.get(token, token)
                    error_message = f"Критическая ошибка при выполнении задачи для аккаунта {user_name_display}: {str(exc)}\n"
                    update_failed_posts_threadsafe(error_message)
                    failed_tokens_set.add(token)
                    progress_bar['value'] = successful_posts_count + failed_posts_count
                    update_progress_label_threadsafe(successful_posts_count, failed_posts_count, total_posts_to_make)
        
        if stop_flag.is_set():
            break

        if autopost_var.get() and successful_tokens_for_this_entry == len(valid_tokens_for_this_entry) and not stop_flag.is_set():
            if photos or videos:
                log_queue.put(f"Пост №{entry_idx + 1} успешно опубликован на всех доступных аккаунтах. Перемещение файлов...\n")
                move_uploaded_files(photos + videos)
            else:
                log_queue.put(f"Пост №{entry_idx + 1} (без файлов) успешно опубликован на всех доступных аккаунтах.\n")

    if failed_tokens_set:
        write_failed_tokens(failed_tokens_set)

    log_queue.put("Публикация завершена.\n")
    if stop_flag.is_set():
        log_queue.put("Процесс был остановлен.\n")
    
    progress_bar['value'] = successful_posts_count + failed_posts_count
    update_progress_label_threadsafe(successful_posts_count, failed_posts_count, total_posts_to_make)

    stop_button['state'] = tk.DISABLED
    post_button['state'] = tk.NORMAL
    add_schedule_button['state'] = tk.DISABLED if autopost_var.get() else tk.NORMAL


def token_display_name(token_value):
    return user_info_dict.get(token_value, token_value)


def write_failed_tokens(failed_tokens_set_param):
    separator = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " ------------------------\n"
    try:
        with open(FAILED_TOKENS_FILE_PATH, 'a', encoding='utf-8') as file:
            file.write(separator)
            for token in failed_tokens_set_param:
                file.write(token + '\n')
        log_queue.put(f"Токены неуспешных публикаций записаны в файл {FAILED_TOKENS_FILE_PATH}\n")
    except Exception as e:
        log_queue.put(f"Ошибка при записи токенов неуспешных публикаций: {e}\n")

def update_progress_label_threadsafe(success_count, failed_count, total_expected_posts):
    remaining_overall = total_expected_posts - (success_count + failed_count)
    progress_info = {
        'success_count': success_count,
        'failed_count': failed_count,
        'remaining_count': remaining_overall
    }
    log_queue.put(('update_progress', progress_info))


def update_successful_posts_threadsafe(message, link):
    log_queue.put(('update_success', message, link))

def update_failed_posts_threadsafe(message):
    log_queue.put(('update_failed', message))

def process_queue():
    try:
        while True:
            item = log_queue.get_nowait()
            if isinstance(item, str):
                output_text.config(state=tk.NORMAL)
                output_text.insert(tk.END, item)
                if auto_scroll_var.get():
                    output_text.see(tk.END)
                output_text.config(state=tk.DISABLED)
            elif isinstance(item, tuple):
                if item[0] == 'update_progress':
                    info = item[1]
                    progress_label.config(text=f"Осталось постов (из общего плана): {info['remaining_count']}")
                    successful_posts_label.config(text=f"Успешные публикации: {info['success_count']}")
                    failed_posts_label.config(text=f"Неуспешные публикации: {info['failed_count']}")
                elif item[0] == 'update_success':
                    message, link = item[1], item[2]
                    update_successful_posts(message, link)
                elif item[0] == 'update_failed':
                    message = item[1]
                    update_failed_posts(message)
    except queue.Empty:
        pass
    root.after(100, process_queue)

def update_successful_posts(message, link):
    global hyperlink_counter
    tag_name = f"hyperlink_{hyperlink_counter}"
    hyperlink_counter += 1
    
    successful_text.config(state=tk.NORMAL)
    successful_text.insert(tk.END, message + " ")
    successful_text.insert(tk.END, link, tag_name)
    successful_text.tag_config(tag_name, foreground=DARK_LINK_COLOR, underline=True)
    successful_text.tag_bind(tag_name, "<Button-1>", lambda e, l=link: webbrowser.open(l))
    successful_text.tag_bind(tag_name, "<Enter>", lambda e: successful_text.config(cursor="hand2"))
    successful_text.tag_bind(tag_name, "<Leave>", lambda e: successful_text.config(cursor="arrow"))
    successful_text.insert(tk.END, "\n")
    if auto_scroll_var.get():
        successful_text.see(tk.END)
    # successful_text.config(state=tk.DISABLED) # Keep NORMAL for links

def update_failed_posts(message):
    failed_text.config(state=tk.NORMAL)
    failed_text.insert(tk.END, message + "\n")
    if auto_scroll_var.get():
        failed_text.see(tk.END)
    failed_text.config(state=tk.DISABLED)


def load_tokens():
    global tokens
    if os.path.exists(TOKENS_FILE_PATH):
        try: # Added try-except for file reading
            with open(TOKENS_FILE_PATH, 'r', encoding='utf-8') as file:
                tokens = [token.strip() for token in file.readlines() if token.strip()]
            tokens_label.config(text=f"Токенов: {len(tokens)}")
            if tokens:
                enable_buttons()
                autopost_check['state'] = tk.NORMAL
            else:
                # messagebox.showwarning("Предупреждение", "Файл не содержит токенов.") # Can be noisy on startup
                log_queue.put("Файл токенов пуст.\n")
                autopost_check['state'] = tk.DISABLED
        except Exception as e:
            log_queue.put(f"Ошибка чтения файла токенов: {e}\n")
            messagebox.showerror("Ошибка токенов", f"Не удалось прочитать файл токенов: {e}")
            tokens = []
            tokens_label.config(text="Токенов: 0 (ошибка)")
            autopost_check['state'] = tk.DISABLED
    else:
        # messagebox.showerror("Ошибка", f"Файл с токенами не найден по пути: {TOKENS_FILE_PATH}")
        log_queue.put(f"Файл с токенами не найден: {TOKENS_FILE_PATH}\n")
        tokens_label.config(text="Токенов: 0 (файл не найден)")
        autopost_check['state'] = tk.DISABLED

def enable_buttons():
    post_button['state'] = tk.NORMAL
    update_add_schedule_button_state()
    remove_schedule_button['state'] = tk.NORMAL


def create_context_menu(widget):
    context_menu = tk.Menu(widget, tearoff=0,
                           bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG,
                           activebackground=DARK_SELECT_BG, activeforeground=DARK_SELECT_FG,
                           disabledforeground=DARK_DISABLED_FG, font=FONT_STYLE)
    context_menu.add_command(label="Вырезать", command=lambda: widget.event_generate("<<Cut>>"))
    context_menu.add_command(label="Копировать", command=lambda: widget.event_generate("<<Copy>>"))
    context_menu.add_command(label="Вставить", command=lambda: widget.event_generate("<<Paste>>"))
    context_menu.add_separator(background=DARK_BACKGROUND)
    context_menu.add_command(label="Выбрать все", command=lambda: widget.event_generate("<<SelectAll>>"))
    
    def toggle_auto_scroll():
        auto_scroll_var.set(not auto_scroll_var.get())

    auto_scroll_label = "Отключить автопрокрутку" if auto_scroll_var.get() else "Включить автопрокрутку"
    context_menu.add_separator(background=DARK_BACKGROUND)
    context_menu.add_command(label=auto_scroll_label, command=toggle_auto_scroll)
    return context_menu

def show_context_menu(event, widget):
    context_menu = create_context_menu(widget)
    context_menu.tk_popup(event.x_root, event.y_root)

def add_posting_set():
    add_window = tk.Toplevel(root)
    add_window.title("Добавить набор публикации")
    add_window.geometry("700x550") # Increased height a bit
    add_window.configure(bg=DARK_BACKGROUND)
    add_window.attributes("-topmost", True)

    # Configure grid weights for content_frame to make sections resizable
    for i in range(6): add_window.grid_rowconfigure(i, weight=0) # Default no weight
    add_window.grid_rowconfigure(2, weight=1) # files_listbox row
    add_window.grid_rowconfigure(4, weight=1) # message_entry row
    add_window.grid_columnconfigure(0, weight=1)
    add_window.grid_columnconfigure(1, weight=0)


    title_label = ttk.Label(add_window, text="Новый набор публикации", font=HEADER_FONT, background=DARK_BACKGROUND, foreground=DARK_FOREGROUND)
    title_label.grid(row=0, column=0, columnspan=3, pady=(10, 5), padx=10, sticky="w")

    files_label_text = ttk.Label(add_window, text="Файлы (Фото и Видео):", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND)
    files_label_text.grid(row=1, column=0, padx=10, pady=5, sticky="w")

    files_listbox_frame = ttk.Frame(add_window) # Frame for listbox and its scrollbar
    files_listbox_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
    files_listbox_frame.grid_rowconfigure(0, weight=1)
    files_listbox_frame.grid_columnconfigure(0, weight=1)

    files_listbox = tk.Listbox(files_listbox_frame, selectmode=tk.MULTIPLE, width=40, height=10, font=FONT_STYLE,
                                bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG, 
                                selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                                borderwidth=1, relief="sunken", exportselection=False)
    files_listbox.grid(row=0, column=0, sticky="nsew")

    files_listbox_scroll = ttk.Scrollbar(files_listbox_frame, orient="vertical", command=files_listbox.yview)
    files_listbox_scroll.grid(row=0, column=1, sticky="nsw")
    files_listbox.config(yscrollcommand=files_listbox_scroll.set)


    files_buttons_frame = ttk.Frame(add_window, style="Dark.TFrame")
    files_buttons_frame.grid(row=2, column=1, padx=5, pady=5, sticky="nw")

    posting_schedule_files = [] 

    def add_files_drop_event(event, file_list_ref, listbox_ref):
        try:
            dropped_files_str = event.data
            if dropped_files_str.startswith('{') and dropped_files_str.endswith('}'):
                processed_path = dropped_files_str[1:-1]
                if '} {' in processed_path:
                     files = [p.strip() for p in re.split(r'(?<=}) (?={)', dropped_files_str) if p.strip()]
                     files = [f[1:-1] if (f.startswith('{') and f.endswith('}')) else f for f in files]
                else:
                     files = [processed_path]
            else:
                files = add_window.tk.splitlist(dropped_files_str)
        except tk.TclError:
            files = [event.data]

        valid_files = [f for f in files 
                       if os.path.exists(f) and f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.mp4', '.avi', '.mov', '.mkv'))]
        
        newly_added_basenames = []
        for file_path in valid_files:
            if file_path not in file_list_ref:
                file_list_ref.append(file_path)
                listbox_ref.insert(tk.END, os.path.basename(file_path))
                newly_added_basenames.append(os.path.basename(file_path))
        if newly_added_basenames:
            log_queue.put(f"Добавлено файлов через DnD: {newly_added_basenames}\n")


    files_listbox.drop_target_register(DND_FILES)
    files_listbox.dnd_bind('<<Drop>>', lambda event: add_files_drop_event(event, posting_schedule_files, files_listbox))

    def add_files_dialog():
        selected_files = filedialog.askopenfilenames(
            title="Выберите фото и/или видео",
            filetypes=[("Image and Video files", "*.jpg;*.jpeg;*.png;*.gif;*.mp4;*.avi;*.mov;*.mkv"), ("All files", "*.*")],
            parent=add_window
        )
        newly_added_basenames = []
        for file in selected_files:
            if file not in posting_schedule_files:
                posting_schedule_files.append(file)
                files_listbox.insert(tk.END, os.path.basename(file))
                newly_added_basenames.append(os.path.basename(file))
        if newly_added_basenames:
            log_queue.put(f"Добавлено файлов через диалог: {newly_added_basenames}\n")

    add_files_button = ttk.Button(files_buttons_frame, text="+", width=3, command=add_files_dialog, style="Dark.TButton")
    add_files_button.pack(side=tk.TOP, pady=(0,2))

    def remove_selected_files_dialog():
        selected_indices = list(files_listbox.curselection())
        selected_indices.sort(reverse=True)
        removed_basenames = []
        for index in selected_indices:
            removed_basenames.append(os.path.basename(posting_schedule_files[index]))
            files_listbox.delete(index)
            del posting_schedule_files[index]
        if removed_basenames:
            log_queue.put(f"Удалены выбранные файлы: {removed_basenames}\n")

    remove_files_button = ttk.Button(files_buttons_frame, text="-", width=3, command=remove_selected_files_dialog, style="Dark.TButton")
    remove_files_button.pack(side=tk.TOP, pady=(2,0))

    text_label = ttk.Label(add_window, text="Текст сообщения:", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND)
    text_label.grid(row=3, column=0, padx=10, pady=(10, 5), sticky="w")

    message_entry_frame = ttk.Frame(add_window) # Frame for text area and its scrollbar
    message_entry_frame.grid(row=4, column=0, columnspan=2, padx=10, pady=5, sticky="nsew")
    message_entry_frame.grid_rowconfigure(0, weight=1)
    message_entry_frame.grid_columnconfigure(0, weight=1)

    message_entry = tk.Text(message_entry_frame, height=5, width=50, font=FONT_STYLE, wrap="word",
                            bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG, insertbackground=DARK_INSERT_BG,
                            selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                            borderwidth=1, relief="sunken")
    message_entry.grid(row=0, column=0, sticky="nsew")
    message_entry.bind("<Button-3>", lambda event: show_context_menu(event, message_entry))

    message_scrollbar = ttk.Scrollbar(message_entry_frame, orient="vertical", command=message_entry.yview)
    message_scrollbar.grid(row=0, column=1, sticky='nsw')
    message_entry.config(yscrollcommand=message_scrollbar.set)

    def paste_message_in_child(entry_widget):
        try:
            clipboard_text = add_window.clipboard_get()
            entry_widget.insert(tk.INSERT, clipboard_text)
        except tk.TclError:
            messagebox.showerror("Ошибка", 
                                 "Буфер обмена пуст или содержит неподдерживаемые данные.", 
                                 parent=add_window)

    paste_button_child = ttk.Button(add_window, text="Вставить", style="Dark.TButton",
                                     command=lambda: paste_message_in_child(message_entry))
    paste_button_child.grid(row=4, column=2, padx=5, pady=(5,0), sticky="nw")

    buttons_frame = ttk.Frame(add_window, style="Dark.TFrame")
    buttons_frame.grid(row=5, column=0, columnspan=3, pady=10, sticky="ew")
    buttons_frame.grid_columnconfigure(0, weight=1) # Make buttons center by distributing space
    buttons_frame.grid_columnconfigure(1, weight=1)
    buttons_frame.grid_columnconfigure(2, weight=1)
    
    inner_buttons_frame = ttk.Frame(buttons_frame, style="Dark.TFrame") # To group buttons for centering
    inner_buttons_frame.pack()


    def save_entry_dialog(close_after=False):
        text = message_entry.get("1.0", tk.END).strip()
        if not posting_schedule_files:
            messagebox.showwarning("Предупреждение", 
                                   "Добавьте хотя бы один файл (фото или видео).", 
                                   parent=add_window)
            return False # Indicate failure
        if not text:
            if not messagebox.askyesno("Подтверждение", 
                                       "Сообщение пустое. Продолжить?", 
                                       parent=add_window):
                return False # Indicate failure
        
        photos_paths = [f for f in posting_schedule_files if f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif'))]
        videos_paths = [f for f in posting_schedule_files if f.lower().endswith(('.mp4', '.avi', '.mov', '.mkv'))]
        
        posting_schedule.append({'text': text, 'photos': photos_paths, 'videos': videos_paths})
        update_posting_schedule_table()
        
        message_entry.delete("1.0", tk.END)
        files_listbox.delete(0, tk.END)
        posting_schedule_files.clear()
        
        log_queue.put("Новый набор публикации сохранен.\n")
        if close_after:
            add_window.destroy()
        return True # Indicate success

    save_and_new_button = ttk.Button(inner_buttons_frame, text="Сохранить и Новый", style="Dark.TButton",
                                      command=lambda: save_entry_dialog(close_after=False))
    save_and_new_button.pack(side=tk.LEFT, padx=5)

    save_button = ttk.Button(inner_buttons_frame, text="Сохранить и Закрыть", style="Dark.TButton",
                              command=lambda: save_entry_dialog(close_after=True))
    save_button.pack(side=tk.LEFT, padx=5)

    cancel_button = ttk.Button(inner_buttons_frame, text="Отмена", style="Dark.TButton", command=add_window.destroy)
    cancel_button.pack(side=tk.LEFT, padx=5)
    
    add_window.focus_set()
    add_window.grab_set() # Make modal


def remove_posting_set():
    selected_items_iids = posting_schedule_table.selection()
    if not selected_items_iids:
        messagebox.showwarning("Предупреждение", "Выберите запись для удаления.")
        return
    try:
        indices_to_remove = sorted([int(iid_str) for iid_str in selected_items_iids], reverse=True)
    except ValueError:
        messagebox.showerror("Ошибка", "Неверный формат IID в таблице.")
        return

    removed_count = 0
    for index in indices_to_remove:
        if 0 <= index < len(posting_schedule):
            del posting_schedule[index]
            log_queue.put(f"Удалена публикация (бывший №{index + 1} в списке до удаления).\n")
            removed_count +=1
        else:
            log_queue.put(f"Ошибка: попытка удаления публикации с индексом {index}, который вне диапазона.\n")
            
    if removed_count > 0:
        update_posting_schedule_table()
    else:
        log_queue.put("Не было удалено ни одной публикации.\n")


def update_posting_schedule_table():
    posting_schedule_table.delete(*posting_schedule_table.get_children())
    for idx, entry in enumerate(posting_schedule):
        photos_count = len(entry.get('photos', []))
        videos_count = len(entry.get('videos', []))
        photos_str = f"{photos_count} фото"
        videos_str = f"{videos_count} видео"
        text_preview = (entry['text'][:30] + '...') if len(entry['text']) > 30 else entry['text']
        # Add tags for alternating row colors if desired, e.g., ('oddrow',) or ('evenrow',)
        posting_schedule_table.insert("", "end", iid=str(idx), values=(idx + 1, photos_str, videos_str, text_preview))


def create_posting_schedule_table():
    table_frame = ttk.Frame(content_frame, style="Dark.TFrame")
    return table_frame

def show_table_context_menu(event, table):
    context_menu = tk.Menu(table, tearoff=0, font=FONT_STYLE,
                           bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG,
                           activebackground=DARK_SELECT_BG, activeforeground=DARK_SELECT_FG,
                           disabledforeground=DARK_DISABLED_FG)
    context_menu.add_command(label="Выбрать все", command=lambda t=table: select_all_table(t))
    context_menu.add_command(label="Удалить выбранные", command=remove_posting_set)
    context_menu.tk_popup(event.x_root, event.y_root)

def select_all_table(table):
    table.selection_set(table.get_children())
    return 'break'

def load_last_post():
    if os.path.exists(LAST_POST_FILE_PATH):
        try:
            with open(LAST_POST_FILE_PATH, 'r', encoding='utf-8') as file:
                value = file.read().strip()
                last_post_var.set(value)
        except Exception as e:
            log_queue.put(f"Ошибка при загрузке последнего выложенного поста: {e}\n")
    else:
        last_post_var.set("")

def save_last_post(*args):
    value = last_post_var.get()
    try:
        with open(LAST_POST_FILE_PATH, 'w', encoding='utf-8') as file:
            file.write(value)
    except Exception as e:
        log_queue.put(f"Ошибка при сохранении последнего выложенного поста: {e}\n")

def save_autopost_state(*args):
    try:
        with open(AUTOPOST_STATE_FILE_PATH, 'w', encoding='utf-8') as file:
            file.write(str(autopost_var.get()))
    except Exception as e:
        log_queue.put(f"Ошибка при сохранении состояния автопубликации: {e}\n")

def load_autopost_state():
    if os.path.exists(AUTOPOST_STATE_FILE_PATH):
        try:
            with open(AUTOPOST_STATE_FILE_PATH, 'r', encoding='utf-8') as file:
                state_str = file.read().strip()
                autopost_var.set(state_str.lower() == 'true')
        except Exception as e:
            log_queue.put(f"Ошибка при загрузке состояния автопубликации: {e}\n")
            autopost_var.set(False)
    else:
        autopost_var.set(False)
    update_add_schedule_button_state()

def save_post_limit_settings(*args):
    try:
        with open(POST_LIMIT_FILE_PATH, 'w', encoding='utf-8') as file:
            file.write(post_limit_var.get())
    except Exception as e:
        log_queue.put(f"Ошибка при сохранении лимита постов: {e}\n")

def save_photo_count_settings(*args):
    try:
        with open(PHOTO_COUNT_FILE_PATH, 'w', encoding='utf-8') as file:
            file.write(photo_count_var.get())
    except Exception as e:
        log_queue.put(f"Ошибка при сохранении количества фотографий: {e}\n")

def load_post_and_photo_settings():
    if os.path.exists(POST_LIMIT_FILE_PATH):
        try:
            with open(POST_LIMIT_FILE_PATH, 'r', encoding='utf-8') as file:
                post_limit_var.set(file.read().strip())
        except Exception as e:
            log_queue.put(f"Ошибка при загрузке лимита постов: {e}\n")
            post_limit_var.set("1")
    else:
        post_limit_var.set("1")

    if os.path.exists(PHOTO_COUNT_FILE_PATH):
        try:
            with open(PHOTO_COUNT_FILE_PATH, 'r', encoding='utf-8') as file:
                photo_count_var.set(file.read().strip())
        except Exception as e:
            log_queue.put(f"Ошибка при загрузке количества фотографий: {e}\n")
            photo_count_var.set("1")
    else:
        photo_count_var.set("1")

tokens = []
posting_schedule = []

root = TkinterDnD.Tk()
root.title("Автопостинг ВКонтакте")
root.configure(bg=DARK_BACKGROUND)


style = ttk.Style()
# Available themes: style.theme_names() -> ('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
# 'clam' or 'alt' are good for customizing. 'default' is also an option.
# Using 'clam' as it's generally available and customizable
try:
    style.theme_use("clam") 
except tk.TclError:
    style.theme_use("default") # Fallback if clam is not available


# --- Configure Styles for Dark Theme ---
style.configure(".", font=FONT_STYLE, background=DARK_BACKGROUND, foreground=DARK_FOREGROUND)
style.configure("TFrame", background=DARK_BACKGROUND)
style.configure("Dark.TFrame", background=DARK_BACKGROUND) # Specific style for frames in add_window

style.configure("TLabel", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND, font=FONT_STYLE)
style.configure("Header.TLabel", font=HEADER_FONT) # For any specific header labels

style.configure("TButton",
                foreground=DARK_BUTTON_FG,
                background=DARK_BUTTON_BG,
                font=FONT_STYLE,
                padding=(10, 5), # Wider padding
                relief="raised",
                borderwidth=1)
style.map("TButton",
          foreground=[("disabled", DARK_DISABLED_FG),
                      ("active", DARK_BUTTON_FG),
                      ("pressed", DARK_BUTTON_FG)],
          background=[("disabled", DARK_DISABLED_BG),
                      ("active", DARK_BUTTON_ACTIVE_BG),
                      ("pressed", DARK_BUTTON_PRESSED_BG)],
          relief=[("pressed", "sunken"), ("!pressed", "raised")])

# Specific style for buttons in add_posting_set for consistency
style.configure("Dark.TButton",
                foreground=DARK_BUTTON_FG,
                background=DARK_BUTTON_BG,
                font=FONT_STYLE,
                padding=(8,4), relief="raised", borderwidth=1)
style.map("Dark.TButton",
          foreground=[("disabled", DARK_DISABLED_FG), ("active", DARK_BUTTON_FG)],
          background=[("disabled", DARK_DISABLED_BG), ("active", DARK_BUTTON_ACTIVE_BG)],
          relief=[("pressed", "sunken"), ("!pressed", "raised")])


style.configure("TLabelFrame", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND, font=FONT_STYLE, borderwidth=1, relief="groove")
style.configure("TLabelFrame.Label", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND, font=HEADER_FONT)

style.configure("TCheckbutton",
                background=DARK_BACKGROUND, foreground=DARK_FOREGROUND,
                indicatorbackground=DARK_WIDGET_BG, indicatorforeground=DARK_PROGRESS_BAR, # Color of the check mark itself
                indicatormargin=3, indicatorrelief="flat", indicatoron=True,
                font=FONT_STYLE)
style.map("TCheckbutton",
          foreground=[("disabled", DARK_DISABLED_FG), ("active", DARK_FOREGROUND)],
          background=[("active", DARK_BACKGROUND)], # Keep background consistent
          indicatorbackground=[("selected", DARK_WIDGET_BG), ("pressed", DARK_WIDGET_BG)],
          indicatorcolor=[("selected", DARK_PROGRESS_BAR), ("pressed", DARK_PROGRESS_BAR)])


style.configure("TEntry",
                fieldbackground=DARK_WIDGET_BG, foreground=DARK_WIDGET_FG,
                insertcolor=DARK_INSERT_BG, # Cursor color
                font=FONT_STYLE, borderwidth=1, relief="sunken")
style.map("TEntry",
          foreground=[("disabled", DARK_DISABLED_FG)],
          fieldbackground=[("disabled", DARK_DISABLED_BG)])


style.configure("Horizontal.TProgressbar", troughcolor=DARK_TROUGH_COLOR, background=DARK_PROGRESS_BAR, thickness=15)
style.configure("Vertical.TScrollbar", troughcolor=DARK_TROUGH_COLOR, background=DARK_WIDGET_BG, arrowcolor=DARK_FOREGROUND, borderwidth=0, relief="flat")
style.map("Vertical.TScrollbar",
    background=[("active", DARK_BUTTON_ACTIVE_BG)],
    arrowcolor=[("pressed", DARK_LINK_COLOR), ("active", DARK_LINK_COLOR)]
)


# Treeview style
style.configure("Treeview",
                background=DARK_WIDGET_BG,
                fieldbackground=DARK_WIDGET_BG, # Row background
                foreground=DARK_WIDGET_FG,
                font=FONT_STYLE,
                rowheight=int(FONT_STYLE[1] * 2.2)) # Adjust row height based on font size
style.map("Treeview",
          background=[("selected", DARK_SELECT_BG)],
          foreground=[("selected", DARK_SELECT_FG)])

style.configure("Treeview.Heading",
                background=DARK_HEADER_BG,
                foreground=DARK_HEADER_FG,
                font=HEADER_FONT,
                relief="raised", padding=(5,3))
style.map("Treeview.Heading",
          background=[("active", DARK_BUTTON_ACTIVE_BG), ("!active", DARK_HEADER_BG)],
          relief=[("active", "sunken"), ("!active", "raised")])

style.configure("TPanedwindow", background=DARK_BACKGROUND)
# --- End Style Configuration ---


window_width = 1250 # Increased width slightly
window_height = 850 # Increased height slightly
root.geometry(f"{window_width}x{window_height}")

screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
position_top = int(screen_height / 2 - window_height / 2)
position_right = int(screen_width / 2 - window_width / 2)
root.geometry(f"{window_width}x{window_height}+{position_right}+{position_top}")


auto_scroll_var = tk.BooleanVar(value=True)
autopost_var = tk.BooleanVar(value=False)
post_limit_var = tk.StringVar(value="1")
photo_count_var = tk.StringVar(value="1")

content_frame = ttk.Frame(root, padding="10 10 10 10", style="TFrame")
content_frame.pack(fill="both", expand=True)

progress_outer_frame = ttk.Frame(content_frame)
progress_outer_frame.pack(pady=5, fill="x")

progress_bar = ttk.Progressbar(progress_outer_frame, length=800, mode='determinate', style="Horizontal.TProgressbar")
progress_bar.pack(pady=(0, 5), fill="x", expand=True)

progress_label = ttk.Label(progress_outer_frame, text="Осталось постов: 0")
progress_label.pack()

info_frame = ttk.Frame(content_frame)
info_frame.pack(pady=5, fill="x")

successful_posts_label = ttk.Label(info_frame, text="Успешные публикации: 0", foreground=DARK_SUCCESS_TEXT_COLOR, font=HEADER_FONT)
successful_posts_label.pack(side=tk.LEFT, padx=(0, 20))

failed_posts_label = ttk.Label(info_frame, text="Неуспешные публикации: 0", foreground=DARK_FAILED_TEXT_COLOR, font=HEADER_FONT)
failed_posts_label.pack(side=tk.LEFT)

last_post_frame = ttk.Frame(content_frame)
last_post_frame.pack(pady=5, fill="x")
last_post_label = ttk.Label(last_post_frame, text="Последний выложенный пост:")
last_post_label.pack(side=tk.LEFT, padx=(0, 10))
last_post_var = tk.StringVar()
last_post_entry = ttk.Entry(last_post_frame, textvariable=last_post_var, width=30)
last_post_entry.pack(side=tk.LEFT)


autopost_settings_frame = ttk.Frame(content_frame)
autopost_settings_frame.pack(pady=5, fill="x")
autopost_check = ttk.Checkbutton(autopost_settings_frame, text="Автопубликация", variable=autopost_var, state=tk.DISABLED)
autopost_check.pack(side=tk.LEFT, padx=(0, 20))
post_limit_label = ttk.Label(autopost_settings_frame, text="Лимит постов:")
post_limit_label.pack(side=tk.LEFT, padx=(0, 5))
post_limit_entry = ttk.Entry(autopost_settings_frame, textvariable=post_limit_var, width=10)
post_limit_entry.pack(side=tk.LEFT, padx=(0, 20))
photo_count_label = ttk.Label(autopost_settings_frame, text="Количество фотографий:")
photo_count_label.pack(side=tk.LEFT, padx=(0, 5))
photo_count_entry = ttk.Entry(autopost_settings_frame, textvariable=photo_count_var, width=10)
photo_count_entry.pack(side=tk.LEFT)


account_management_frame = ttk.Frame(content_frame)
account_management_frame.pack(pady=5, fill="x")
account_button = ttk.Button(account_management_frame, text="Указать аккаунты", command=load_tokens)
account_button.pack(side=tk.LEFT, padx=(0, 10))
tokens_label = ttk.Label(account_management_frame, text="Токенов: 0")
tokens_label.pack(side=tk.LEFT)

root.drop_target_register(DND_FILES)
root.dnd_bind('<<Drop>>', lambda event: None)

controls_frame = ttk.Frame(content_frame) # This frame will be centered using pack
controls_frame.pack(pady=10)

post_button = ttk.Button(controls_frame, text="Выложить пост", command=submit_post, state=tk.DISABLED)
post_button.pack(side=tk.LEFT, padx=(0, 10))
stop_button = ttk.Button(controls_frame, text="Остановить", command=stop_posting, state=tk.DISABLED)
stop_button.pack(side=tk.LEFT, padx=(0, 10))
add_schedule_button = ttk.Button(controls_frame, text="Добавить набор публикаций", command=add_posting_set, state=tk.DISABLED)
add_schedule_button.pack(side=tk.LEFT, padx=(0, 10))
remove_schedule_button = ttk.Button(controls_frame, text="Удалить выбранные", command=remove_posting_set, state=tk.DISABLED)
remove_schedule_button.pack(side=tk.LEFT, padx=(0, 10))


paned_window_schedule_log = ttk.PanedWindow(content_frame, orient=tk.HORIZONTAL)
paned_window_schedule_log.pack(pady=10, fill="both", expand=True)

left_table_frame_outer = ttk.LabelFrame(paned_window_schedule_log, text="План публикаций", padding=5)
paned_window_schedule_log.add(left_table_frame_outer, weight=1)

posting_schedule_table = ttk.Treeview(left_table_frame_outer, 
                                      columns=("№", "Фото", "Видео", "Текст"), 
                                      show='headings', 
                                      selectmode='extended', 
                                      height=8) # Adjusted height
for col_name in ("№", "Фото", "Видео", "Текст"):
    posting_schedule_table.heading(col_name, text=col_name)
    if col_name == "Текст":
        posting_schedule_table.column(col_name, width=250, minwidth=150, stretch=tk.YES)
    elif col_name == "№":
        posting_schedule_table.column(col_name, width=40, minwidth=30, stretch=tk.NO, anchor=tk.CENTER)
    else:
        posting_schedule_table.column(col_name, width=100, minwidth=70, stretch=tk.NO, anchor=tk.W)
posting_schedule_table.pack(side=tk.LEFT, fill="both", expand=True)
scrollbar_table = ttk.Scrollbar(left_table_frame_outer, orient="vertical", command=posting_schedule_table.yview)
scrollbar_table.pack(side=tk.RIGHT, fill="y")
posting_schedule_table.config(yscrollcommand=scrollbar_table.set)
posting_schedule_table.bind("<Button-3>", lambda event: show_table_context_menu(event, posting_schedule_table))
posting_schedule_table.bind("<Control-a>", lambda event: select_all_table(posting_schedule_table))
posting_schedule_table.bind("<Control-A>", lambda event: select_all_table(posting_schedule_table))
posting_schedule_table.bind("<Delete>", lambda event: remove_posting_set())

right_log_frame_outer = ttk.LabelFrame(paned_window_schedule_log, text="Лог процесса", padding=5)
paned_window_schedule_log.add(right_log_frame_outer, weight=1)
output_text_frame = ttk.Frame(right_log_frame_outer)
output_text_frame.pack(pady=(0,0), fill="both", expand=True)
output_text = tk.Text(output_text_frame, width=50, height=8, font=FONT_STYLE, wrap="word", 
                      bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG, insertbackground=DARK_INSERT_BG,
                      selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                      borderwidth=1, relief="sunken", state=tk.DISABLED) # Start disabled
output_text.pack(side=tk.LEFT, fill="both", expand=True)
output_scrollbar = ttk.Scrollbar(output_text_frame, orient="vertical", command=output_text.yview)
output_scrollbar.pack(side=tk.RIGHT, fill="y")
output_text.config(yscrollcommand=output_scrollbar.set)
output_text.bind("<Button-3>", lambda event: show_context_menu(event, output_text))


paned_window_results = ttk.PanedWindow(content_frame, orient=tk.HORIZONTAL)
paned_window_results.pack(pady=10, fill="both", expand=True)

successful_frame = ttk.LabelFrame(paned_window_results, text="Успешные публикации", padding=5)
paned_window_results.add(successful_frame, weight=1)
successful_text_inner_frame = ttk.Frame(successful_frame)
successful_text_inner_frame.pack(fill="both", expand=True)
successful_text = tk.Text(successful_text_inner_frame, width=40, height=8, font=FONT_STYLE, wrap="word", 
                           bg=DARK_ACCENT_SUCCESS_BG, fg=DARK_FOREGROUND, insertbackground=DARK_INSERT_BG,
                           selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                           borderwidth=1, relief="sunken", cursor="arrow") # state=tk.NORMAL for links
successful_text.pack(side=tk.LEFT, fill="both", expand=True)
successful_scrollbar = ttk.Scrollbar(successful_text_inner_frame, orient="vertical", command=successful_text.yview)
successful_scrollbar.pack(side=tk.RIGHT, fill="y")
successful_text.config(yscrollcommand=successful_scrollbar.set)
successful_text.bind("<Button-3>", lambda event: show_context_menu(event, successful_text))


failed_frame = ttk.LabelFrame(paned_window_results, text="Неуспешные публикации", padding=5)
paned_window_results.add(failed_frame, weight=1)
failed_text_inner_frame = ttk.Frame(failed_frame)
failed_text_inner_frame.pack(fill="both", expand=True)
failed_text = tk.Text(failed_text_inner_frame, width=40, height=8, font=FONT_STYLE, wrap="word", 
                       bg=DARK_ACCENT_FAILED_BG, fg=DARK_FOREGROUND, insertbackground=DARK_INSERT_BG,
                       selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                       borderwidth=1, relief="sunken", state=tk.DISABLED) # Start disabled
failed_text.pack(side=tk.LEFT, fill="both", expand=True)
failed_scrollbar = ttk.Scrollbar(failed_text_inner_frame, orient="vertical", command=failed_text.yview)
failed_scrollbar.pack(side=tk.RIGHT, fill="y")
failed_text.config(yscrollcommand=failed_scrollbar.set)
failed_text.bind("<Button-3>", lambda event: show_context_menu(event, failed_text))


load_last_post()
last_post_var.trace_add('write', save_last_post)
load_autopost_state()
autopost_var.trace_add('write', save_autopost_state)
autopost_var.trace_add('write', update_add_schedule_button_state)
load_post_and_photo_settings()
post_limit_var.trace_add('write', save_post_limit_settings)
photo_count_var.trace_add('write', save_photo_count_settings)
load_tokens()

root.after(100, process_queue)
root.mainloop()